# -*- coding: utf-8 -*-
import datetime
import json
import logging
from decimal import Decimal

from sqlalchemy import func

from async import async_job
from common import orm, slave
from common.account.model.account import Account
from common.cache import redis_cache
from common.cache.redis_cache import count_unread_win, count_ongoing_track, remove_unread_win, add_ongoing_track
from common.lottery import LOTTERY_TYPE
from common.lottery.cyclical import ACTIVITY_STATUS, TRACK_STATUS, ORDER_STATUS
from common.lottery.cyclical.model import ACTIVITY_MODEL, ORDER_MODEL
from common.lottery.model import OrderIndex, TrackIndex
from common.preset.db import lottery as preset_db
from common.transaction.db import create_sharding_transaction
from common.transaction.model import TRANSACTION_TYPE, TRANSACTION_STATUS
from common.utils import EnhencedEncoder
from common.utils import tz
from common.utils.db import list_object, generate_filter  # for admin console
from common.utils.decorator import sql_wrapper

_LOGGER = logging.getLogger('bigbang')


@sql_wrapper
def get_order_index(activity_type, order_id):
    order_index = OrderIndex.query.filter(
        OrderIndex.activity_type == activity_type).filter(
        OrderIndex.order_id == order_id).first()
    return order_index


@sql_wrapper
def get_activity(activity_id, activity_type):
    table = ACTIVITY_MODEL[activity_type]
    ac = table.query.filter(
        table.id == activity_id).one()
    return ac


@sql_wrapper
def get_unstopped_activity(activity_type):
    table = ACTIVITY_MODEL[activity_type]
    query = table.query.filter(table.status == ACTIVITY_STATUS.STARTED).all()
    return query


@sql_wrapper
def get_activity_by_term(term, activity_type):
    table = ACTIVITY_MODEL[activity_type]
    ac = table.query.filter(
        table.term == term).one()
    return ac


@sql_wrapper
def get_summary(lottery_list):
    """ 每种类型返回最近3期的数据
    """
    resp = {}
    for lottery in lottery_list:
        lottery_type = lottery.type
        if lottery_type not in LOTTERY_TYPE.values():
            continue
        resp[lottery_type] = []
        ac = ACTIVITY_MODEL[lottery_type]
        items = ac.query.filter(
            ac.status == ACTIVITY_STATUS.ANNOUNCED).order_by(
            ac.id.desc()).limit(3).all()
        for item in items:
            resp[lottery_type].append(item.as_dict())

    return resp


@sql_wrapper
def create_track_index(activity_type, user_id, track_id, total_count, total_amount, tracked_count, tracked_amount):
    inst = TrackIndex()
    inst.activity_type = activity_type
    inst.user_id = user_id
    inst.track_id = track_id
    inst.save()
    redis_cache.create_track_info(track_id)
    # update track info to cache
    redis_cache.increase_track_info(track_id, 'total_count', total_count)
    redis_cache.increase_track_info(track_id, 'total_amount', total_amount)
    redis_cache.increase_track_info(track_id, 'bet_count', tracked_count)
    redis_cache.increase_track_info(track_id, 'bet_amount', tracked_amount)

    add_ongoing_track(user_id, track_id)

    from async import async_job
    async_job.generate_track_detail(track_id)


def get_track_detail(user_id, activity_type, track_id):
    track = TrackIndex.query.filter(TrackIndex.track_id == track_id).one()
    info = json.loads(track.detail)
    info['created_at'] = tz.utc_str_to_local_str(info['created_at'])
    info['status'] = track.status
    return info


def get_track_info_by_order(order, activity_type):
    if order.track_id is None:
        return {}
    # calculate from redis cache
    cached_track_info = redis_cache.get_track_info(order.track_id)
    if cached_track_info:
        _LOGGER.info('get_track_info_by_order from cache, track_id:%s', order.track_id)
        total_count = cached_track_info.get('total_count', 0)
        total_amount = cached_track_info.get('total_amount', 0)
        bet_count = cached_track_info.get('bet_count', 0)
        bet_amount = cached_track_info.get('bet_amount', 0)
        win_price = cached_track_info.get('win_price', 0)
        bonus = cached_track_info.get('bonus', 0)
    else:
        _LOGGER.info('get_track_info_by_order from db, track_id:%s', order.track_id)
        impl = ORDER_MODEL[activity_type]
        total_count, total_amount = slave.session.query(
            func.count(impl.id), func.sum(impl.price)).filter(
            impl.track_id == order.track_id
        ).first()
        bet_count, bet_amount = slave.session.query(
            func.count(impl.id), func.sum(impl.price)
        ).filter(impl.track_id == order.track_id).filter(
            impl.track_status.in_([TRACK_STATUS.TRACKED, TRACK_STATUS.WINNED])
        ).first()
        win_price, bonus = slave.session.query(
            func.sum(impl.win_price), func.sum(impl.bonus)
        ).filter(impl.track_id == order.track_id).first()
    return {
        'total_count': total_count or 0,
        'total_amount': total_amount or 0,
        'bet_count': bet_count or 0,
        'bet_amount': bet_amount or 0,
        'type': order.type,
        'activity_type': activity_type,
        'win_price': win_price,
        'bonus': bonus
    }


@sql_wrapper
def get_track_history(user_id, params, limit, offset):
    query = TrackIndex.query.filter(
        TrackIndex.user_id == user_id).filter(
        TrackIndex.created_at >= params['start_date']).filter(
        TrackIndex.activity_type <= params['max_type'])
    if params.get('status') is not None:
        query = query.filter(TrackIndex.status == params['status'])
    query = query.order_by(TrackIndex.id.desc())
    if limit:
        query = query.limit(limit)
    if offset:
        query = query.offset(offset)
    items = query.all()
    resp = []
    for item in items:
        if not item.detail:
            continue
        info = json.loads(item.detail)
        info['created_at'] = tz.utc_str_to_local_str(info['created_at'])
        info['status'] = item.status
        resp.append(info)

    return resp


@sql_wrapper
def generate_track_detail(track_id):
    track_index = TrackIndex.query.filter(TrackIndex.track_id == track_id).one()
    impl = ORDER_MODEL[track_index.activity_type]
    inst = impl.query.filter(impl.track_id == track_id).first()
    if not inst:
        _LOGGER.warn('generate_track_detail error, track_id %s not found in order %s',
                     track_id, track_index.activity_type)
        return
    info = inst.as_dict()
    track_info = get_track_info_by_order(inst, track_index.activity_type)
    info.update(track_info)
    info['status'] = track_index.status
    track_index.detail = json.dumps(info, ensure_ascii=False, cls=EnhencedEncoder)
    track_index.save()


@sql_wrapper
def get_order_history(user_id, params, limit, offset):
    # FIXME： limit, size的分页在这里其实是有问题的，后续换成since_id, max_id
    query = OrderIndex.query.filter(
        OrderIndex.user_id == user_id).filter(
        OrderIndex.created_at >= params['start_date']).filter(
        OrderIndex.activity_type <= params['max_type'])
    if params.get('status') is not None:
        query = query.filter(OrderIndex.status == params['status'])
    query = query.order_by(OrderIndex.id.desc())
    if limit:
        query = query.limit(limit)
    if offset:
        query = query.offset(offset)
    items = query.all()

    resp = []
    for item in items:
        impl = ORDER_MODEL[item.activity_type]
        data = impl.query.filter(impl.id == item.order_id).first()
        data = data.as_dict()
        data['order_id'] = data['id']
        data['activity_type'] = item.activity_type
        data['status'] = item.status
        data['created_at'] = tz.utc_to_local_str(item.created_at)
        resp.append(data)
        if item.status == TRACK_STATUS.WINNED:
            remove_unread_win(item.user_id, item.activity_type, item.order_id)
    return resp


@sql_wrapper
def list_all_orders(query_dct):
    hint_index = None
    if 'user_id' in query_dct:
        hint_index = (OrderIndex, 'USE INDEX (time_idx)')
    elif 'created_at' in query_dct:
        hint_index = (OrderIndex, 'USE INDEX (idx_created_at)')
    return list_object(query_dct, OrderIndex, total_count=True, hint_index=hint_index)

@sql_wrapper
def check_order(params_dct):
    index = OrderIndex.query.with_for_update().filter(OrderIndex.id == params_dct['id']).one()
    model = ORDER_MODEL[index.activity_type]
    order = model.query.with_for_update().filter(model.id == index.order_id).one()
    account = Account.query.with_for_update().filter(Account.id == order.user_id).one()
    if params_dct['status'] == ORDER_STATUS.FAIL:
        # 审核不通过
        order.status = index.status = ORDER_STATUS.FAIL
        order.save(auto_commit=False)
        index.save(auto_commit=False)
        orm.session.commit()
    elif params_dct['status'] == ORDER_STATUS.WINNED and order.status == ORDER_STATUS.UNCHECK:
        # 审核通过
        order.status = index.status = ORDER_STATUS.WINNED
        extend = json.loads(order.extend or '{}')
        account.balance += (order.win_price + order.bonus)
        account.withdraw += (order.win_price + Decimal(extend.get('withdraw_bonus', 0)))
        create_sharding_transaction(order.user_id, {'type': TRANSACTION_TYPE.AWARD,
                                                    'title': u'{}派奖'.format(
                                                        LOTTERY_TYPE.get_label(index.activity_type)),
                                                    'activity_type': index.activity_type,
                                                    'price': order.win_price + order.bonus,
                                                    'balance': account.balance,
                                                    'order_id': order.id,
                                                    'status': TRANSACTION_STATUS.DONE,
                                                    })

        order.save(auto_commit=False)
        index.save(auto_commit=False)
        orm.session.commit()
        try:
            async_job.stats_order_win.delay(order.id, index.activity_type)
        except Exception as e:
            _LOGGER.exception(e)


@sql_wrapper
def get_user_read_stats(user_id):
    return {
        'win': count_unread_win(user_id),
        'tracking': count_ongoing_track(user_id)
    }


def update_order_status(order_index_id, status):
    order_index = OrderIndex.query.with_for_update().filter(OrderIndex.id == order_index_id).one()
    if not order_index_id:
        return
    order_table = ORDER_MODEL[order_index.activity_type]
    order = order_table.query.with_for_update().filter(order_table.id == order_index.order_id).one()
    order_index.status = order.status = status
    order_index.save(auto_commit=False)
    order.save(auto_commit=False)
    orm.session.commit()


def check_stop_lottery(activity, chn='', cvc=''):
    start_stop_lottery, end_stop_lottery = get_preset_stop_lottery(activity, chn, cvc)
    now = datetime.datetime.now()
    if end_stop_lottery and start_stop_lottery:
        return (end_stop_lottery > now > start_stop_lottery)
    return False


def get_preset_stop_lottery(activity, chn, cvc):
    device_type = 1
    if chn and chn.startswith('ios'):
        device_type = 2
    for each in preset_db.get_activity_lottery(int(activity)):
        if cvc and (not (each.min_cvc <= cvc <= each.max_cvc)):
            continue
        if not device_type & each.device_type:
            continue
        if chn and (',%s,' % chn in each.exclude_chn):
            continue
        return each.start_stop_lottery, each.end_stop_lottery
    return None, None
